package scan;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.zip.Adler32;
import java.util.zip.CheckedInputStream;

/**
 * Collects the data
 * 
 * @author Burn
 * 
 */
public class Scan extends Thread {

	private static int initialDepth;
	private static final long MB = 1024 * 1024;
	private static SharedData sd;
	private int id = 0;
	private PrintWriter ostr;
	private File folderToCheck;
	private boolean toFile;
	private Date dStartScan;
	private Float fSizeInMB;
	private int depthIncrement = 1;
	private Integer iSizeInMB;
	private int items;
	private int numberOfFiles;
	private int numberOfFolders;
	private int propItems;
	private long cksum;
	private Long lSizeInB;
	private long propSizeInB;
	private SimpleDateFormat simpleDateFormatter;
	private SimpleDateFormat simpleTimeFormatter;
	private SimpleDateFormat smallTimeFormatter;
	private String fileType;
	private String sScanDate;
	private String sScanTime;
	private volatile Thread blinker;
	private FileTypes ft;

	public Scan() {
		sd = new SharedData();
	}

	/** Creates a new instance */
	public Scan(File folderToCheck) {
		this();

		simpleDateFormatter = new SimpleDateFormat("yyyy-MM-dd");
		simpleTimeFormatter = new SimpleDateFormat("HH:mm:ss");
		smallTimeFormatter = new SimpleDateFormat("mm:ss");

		dStartScan = new Date();
		sScanDate = simpleDateFormatter.format(dStartScan);
		sScanTime = simpleTimeFormatter.format(dStartScan);

		String filename = folderToCheck.toString();

		if (File.separator.compareTo("\\") == 0) {
			if (filename.endsWith(":")) {
				folderToCheck = new File(filename + "\\");
			}
		}

		this.folderToCheck = folderToCheck;
	}

	/**
	 * Scan To File
	 * 
	 * @param folderToCheck
	 * @param outputFile
	 * @param guits
	 */
	public Scan(File folderToCheck, File outputFile) {
		this(folderToCheck);
		toFile = true;

		sd.setNode(outputFile);

		try {
			if (sd.getUTF8()) {
				ostr = new PrintWriter(new OutputStreamWriter(
						new FileOutputStream(outputFile), "UTF8"), true);
			} else {
				ostr = new PrintWriter(new BufferedOutputStream(
						new FileOutputStream(outputFile), 128), true);
			}

			for (int i = 0; i < SharedData.columnsHeaders.length; i++) {
				ostr.print(SharedData.columnsHeaders[i]);

				if (i < (SharedData.columnsHeaders.length - 1)) {
					ostr.print("\t");
				}
			}

			ostr.println();
		} catch (IOException ioe) {
			System.err.println(ioe);
			System.exit(1);
		}
	}

	public void start() {
		blinker = new Thread(this);
		blinker.start();
	}

	public void stop_() {
		blinker = null;
	}

	synchronized public void run() {
		try {
			ft = new FileTypes();

			if (File.separator.compareTo("/") == 0) {
				depthIncrement = 0;
			}

			initialDepth = initialDepth(folderToCheck.toString());

			propSizeInB = 0;
			fSizeInMB = new Float(0);

			System.out.println("Scanning \"" + folderToCheck.toString()
					+ "\"...");

			processFile(folderToCheck, 0);

			Date dEndScan = new Date();
			Date dDiffScan = new Date(dEndScan.getTime() - dStartScan.getTime());
			String elapsedTime = smallTimeFormatter.format(dDiffScan);
			System.out.println("> ElapsedTime = " + elapsedTime);

			notifyAll();
		} catch (OutOfMemoryError e) {
			System.err.println("Error : " + e);
			System.exit(1);
		}
	} // run

	private long processFile(File fileToCheck, int depth) {
		Date dFileLastModified;
		int indexSeparator = 0;
		int rowID = id;
		Integer iID = Integer.valueOf(rowID + 1);
		String dateModified = "";
		String fileName = "";
		String Path = "";
		String fileExtension = "";
		String timeModified = "";

		if (!sd.depthLimit() || (sd.depthLimit() && (sd.getDepth() > depth))) {
			if (fileToCheck.isDirectory() || sd.filesAndFolders()) {
				id++;
				indexSeparator = fileToCheck.toString().lastIndexOf(
						File.separatorChar);
				Path = fileToCheck.toString().substring(0, indexSeparator + 1);
				fileName = fileToCheck.toString().substring(indexSeparator + 1);

				dFileLastModified = new Date(fileToCheck.lastModified());
				dateModified = simpleDateFormatter.format(dFileLastModified);
				timeModified = simpleTimeFormatter.format(dFileLastModified);

				// if (gui && !toFile) {
				// guits.getDefaultTableModel().addRow((Object[]) null);
				// guits.getDefaultTableModel().setValueAt(iID, rowID,
				// GUITinyScan.COLUMN_ID);
				// guits.getDefaultTableModel().setValueAt("", rowID,
				// GUITinyScan.COLUMN_NODE);
				// guits.getDefaultTableModel().setValueAt(sScanDate, rowID,
				// GUITinyScan.COLUMN_SCAN_DATE);
				// guits.getDefaultTableModel().setValueAt(sScanTime, rowID,
				// GUITinyScan.COLUMN_SCAN_TIME);
				// guits.getDefaultTableModel().setValueAt(Path, rowID,
				// GUITinyScan.COLUMN_PATH);
				// guits.getDefaultTableModel().setValueAt(fileName, rowID,
				// GUITinyScan.COLUMN_NAME);
				//
				// if (fileToCheck.isDirectory()) {
				// guits.getDefaultTableModel().setValueAt("", rowID,
				// GUITinyScan.COLUMN_EXTENSION);
				// guits.getDefaultTableModel().setValueAt("Folder",
				// rowID, GUITinyScan.COLUMN_TYPE);
				// guits.getDefaultTableModel().setValueAt(
				// Boolean.valueOf("true"), rowID,
				// GUITinyScan.COLUMN_FOLDER);
				// }
				//
				// guits.getDefaultTableModel().setValueAt(
				// Integer.valueOf(Path.length() + fileName.length()),
				// rowID, GUITinyScan.COLUMN_FULL_PATH_LENGTH);
				//
				// guits.getDefaultTableModel().setValueAt(dateModified,
				// rowID, GUITinyScan.COLUMN_DATE);
				// guits.getDefaultTableModel().setValueAt(timeModified,
				// rowID, GUITinyScan.COLUMN_TIME);
				// }
			}

			getFileProperties(fileToCheck, depth);
			items = propItems;

			if (fileToCheck.isDirectory() || sd.filesAndFolders()) {
				if (fileToCheck.isDirectory()) {
					lSizeInB = Long.valueOf(propSizeInB);

					if (propSizeInB == -1) {
						fSizeInMB = new Float(-1);
						items = -1;
					} else {
						fSizeInMB = new Float(propSizeInB / (float) MB);
					}

					numberOfFolders++;

					if (sd.depthLimit()) {
						lSizeInB = Long.valueOf(-1);
						fSizeInMB = new Float(-1);
						items = -1;
					}
				} else if (fileToCheck.isFile()) {
					numberOfFiles++;

					int indexDot = fileToCheck.toString().lastIndexOf(".");

					if ((indexDot != -1) && (indexDot > indexSeparator)) {
						fileExtension = fileToCheck.toString().substring(
								indexDot + 1);

						// if (gui && !toFile) {
						// guits.getDefaultTableModel().setValueAt(
						// fileExtension, rowID,
						// GUITinyScan.COLUMN_EXTENSION);
						// }
					} else {
						// if (gui && !toFile) {
						// guits.getDefaultTableModel().setValueAt("", rowID,
						// GUITinyScan.COLUMN_EXTENSION);
						// }
					}

					lSizeInB = Long.valueOf(fileToCheck.length());
					fSizeInMB = new Float(fileToCheck.length() / (float) MB);

					// if (gui && !toFile) {
					// fileType = (String) ft.getHashMap().get(
					// fileExtension.toLowerCase());
					//
					// if (fileType == null) {
					// guits.getDefaultTableModel().setValueAt("File",
					// rowID, GUITinyScan.COLUMN_TYPE);
					// } else {
					// guits.getDefaultTableModel().setValueAt(fileType,
					// rowID, GUITinyScan.COLUMN_TYPE);
					// }
					//
					// guits.getDefaultTableModel().setValueAt(
					// Boolean.valueOf("false"), rowID,
					// GUITinyScan.COLUMN_FOLDER);
					// }
				}

				iSizeInMB = Integer.valueOf(fSizeInMB.intValue());

				if (toFile) {
					ostr.print(iID + "\t");
					ostr.print("\"" + Path + "\"\t");
					ostr.print("\"" + fileName + "\"\t");

					if (fileToCheck.isFile()) {
						ostr.print("\"" + fileExtension + "\"\t");
					} else {
						ostr.print("\"" + "" + "\"\t");
					}

					if (fileToCheck.isDirectory()) {
						ostr.print("\"true\"\t");
						ostr.print("\"Folder\"\t");
					} else {
						ostr.print("\"false\"\t");
						fileType = (String) ft.getHashMap().get(
								fileExtension.toLowerCase());

						if (fileType == null) {
							ostr.print("\"File\"\t");
						} else {
							ostr.print("\"" + fileType + "\"\t");
						}
					}

					ostr.print(iSizeInMB + "\t");
					ostr.print(lSizeInB + "\t");

					if (fileToCheck.isDirectory()) {
						ostr.print(Integer.valueOf(items) + "\t");
					} else {
						ostr.print("0\t");
					}

					ostr.print(dateModified + "\t");
					ostr.print(timeModified + "\t");
					ostr.print(Integer.valueOf(depth + initialDepth) + "\t");
					ostr.print(Integer.valueOf(Path.length()
							+ fileName.length())
							+ "\t");

					if (sd.checksum() && fileToCheck.isFile()) {
						ostr.print(Long.valueOf(checksum(fileToCheck)) + "\t");
					} else {
						ostr.print("-1\t");
					}

					ostr.print("\"false\"\t");

					ostr.print(sScanDate + "\t");
					ostr.print(sScanTime + "\t");
					ostr.print("\"" + sd.getNode() + "\"");
					ostr.println();

					// System.out.println(ostr.toString());
				}
			}
		}

		return propSizeInB;
	} // processFile

	private void getFileProperties(File file, int depth) {
		long sizeInBDir = 0;
		int itemsInDir = 0;

		try {
			// this won't work for a broken link
			// I will change the code here once the JDK7 is out (isSymbolicLink
			// method)
			if (!file.getCanonicalFile().equals(file)) { // a symbolic link
															// (Linux)
				propSizeInB = file.length();
				propItems = 1;

				return;
			} // if symbolic link
			else if (file.isDirectory()) {
				depth++;

				File[] files = file.listFiles();

				if (files != null) {

					for (int i = 0; i < files.length; i++) {
						long sizeInB = processFile(files[i], depth);

						if (sizeInB != -1) {
							sizeInBDir += sizeInB;
							itemsInDir += items;

							if (files[i].isDirectory()) {
								itemsInDir++;
							}
						}
					}

					propSizeInB = sizeInBDir;
					propItems = itemsInDir;

					return;
				} // if

				// we should never get here
				propSizeInB = -1;

				return;
			} // if directory
			else if (file.isFile()) {
				// a regular file
				propSizeInB = file.length();
				propItems = 1;

				return;
			}
		} catch (java.io.IOException e) { // a broken symbolic link...
			System.err.println("Error : " + e);
		}
	}

	private int initialDepth(String path) {
		int numberOfSeparators = 0;

		for (int i = 0; i < path.length(); i++) {
			Character p = Character.valueOf(path.charAt(i));

			if (p.compareTo(Character.valueOf(File.separatorChar)) == 0) {
				numberOfSeparators++;
			}
		}

		if (path.startsWith(File.separator + File.separator)) { // drive mapped
			numberOfSeparators = numberOfSeparators - 2;
		}

		if (path.endsWith(File.separator)) {
			numberOfSeparators--;
		}

		return numberOfSeparators + depthIncrement;
	}

	private long checksum(File fl) {
		CheckedInputStream cis = null;

		try {
			// Compute Adler-32 checksum
			cis = new CheckedInputStream(new FileInputStream(fl), new Adler32());

			byte[] tempBuf = new byte[128];

			while (cis.read(tempBuf) >= 0) {
			}

			cksum = cis.getChecksum().getValue();

			return cksum;
		} catch (IOException e) {
			System.err.println(e);
		} finally {
			try {
				cis.close();
			} catch (IOException ioe) {
				System.err.println("writeFile: caught i/o exception");
				System.err.println(ioe);
			} catch (NullPointerException np) {
				System.err.println(np);
			}
		}

		cksum = -1;

		return cksum;
	}
}
